Game Server Services(GS2)を使ってUnityでログインボーナスを実装してみた
こんにちは、ゲームソリューション部の入井です。
今回は、GS2-LoginRewardを使ってモバイルゲームなどでよくあるログインボーナス機能をUnityで実装してみたので、その内容をご紹介します。
環境
- Unity 2022.3.18f1
- UniTask 2.5.0
- GS2 C# SDK 2023.12.10
- GS2 SDK for Unity 2023.12.14
GS2-LoginRewardの報酬提供方式
ログインボーナスは、一般的にはユーザーの継続率を高めるため、毎日のログインごとにゲーム内通貨等の報酬を付与する機能であり、様々な運用型ゲームで取り入れられています。
GS2においては、認証済のユーザーが専用のAPIにアクセスすることで、そのユーザーに対して何らかの入手アクションを与えるという仕組みになっています。APIを通しての報酬獲得は1日に1度のみ実行可能です。
入手アクションについては、以下の記事でも解説しています。
GS2-LoginRewardでは、スケジュールモードとストリーミングモードという2つのモードが用意されており、それぞれ報酬付与の考え方が異なります。
スケジュールモード
スケジュールモードは、特定の日付ごとに報酬内容が決まっている方式です。例えば、10月1日はジェムを500個、10月2日はチケット1枚、10月3日はコイン1,000枚・・・という形です。
例えば、特定のイベント期間中にログインしてくれたユーザーに対し、日付毎に異なる報酬を用意したい場合に使用できます。
なお、スケジュール期間の設定のためにはGS2-Scheduleと連携する必要があります。
ストリーミングモード
ストリーミングモードは、特定の日付は設定されておらず、報酬の順番のみ決まっている方式です。特定の日付に対し報酬が結びついている訳ではなく、例えばログイン1日目はジェムを500個、2日目はチケット1枚、3日目はコイン1,000枚・・・という形で設定されている場合、10月1日にログインしてジェム500個を取得後、3日空けて10月4日にログインしても2日目のチケット1枚を獲得することになります。最後の報酬獲得後、また1日目からやり直す設定も可能です。
例えば、定常的に設置するデイリーボーナスや、イベント中でも特定の日付に紐付けずに報酬を与えたい場合に使用できます。
なお、今回は使用しませんがGS2-LoginRewardでは取り逃した報酬を後からでも獲得できる『見逃し補償』という機能も用意されています。
今回作成したもの
今回は、プレイヤーの所持品を管理するサービスであるGS2-Inventoryとログインボーナス機能を提供するGS2-LoginReward(+GS2-Schedule)を組み合わせて、スケジュールモードとストリーミングモードそれぞれでアイテム報酬を受け取り、その結果を画面に描画する機能をUnityで作成しました。
GS2の各サービスのマスタ設定
今回ログインボーナス機能を実現するために使用したGS2の各サービスのマスタ設定内容を紹介していきます。
GS2-Inventoryのマスタ設定
プレイヤーが所有するLoginRewardTest
というインベントリ内で、ログインボーナスで受け取る各アイテムの情報を定義しています。今回はコインとジェムとチケットを報酬として受け取る想定です。
{
"version": "2019-02-05",
"inventoryModels": [
{
"inventoryModelId": "grn:gs2:ap-northeast-1:XXX:inventory:game-001:model:LoginRewardTest",
"name": "LoginRewardTest",
"initialCapacity": 10,
"maxCapacity": 10,
"protectReferencedItem": false,
"itemModels": [
{
"itemModelId": "grn:gs2:ap-northeast-1:XXX:inventory:game-001:model:LoginRewardTest:item:coin",
"name": "coin",
"metadata": "{\"displayName\": \"コイン\"}",
"stackingLimit": 10000,
"allowMultipleStacks": false,
"sortValue": 0
},
{
"itemModelId": "grn:gs2:ap-northeast-1:XXX:inventory:game-001:model:LoginRewardTest:item:gem",
"name": "gem",
"metadata": "{\"displayName\": \"ジェム\"}",
"stackingLimit": 10000,
"allowMultipleStacks": false,
"sortValue": 0
},
{
"itemModelId": "grn:gs2:ap-northeast-1:XXX:inventory:game-001:model:LoginRewardTest:item:ticket",
"name": "ticket",
"metadata": "{\"displayName\": \"チケット\"}",
"stackingLimit": 10000,
"allowMultipleStacks": false,
"sortValue": 0
}
]
}
]
}
GS2-Scheduleのマスタ設定
GS2-LoginRewardのスケジュールモードと連携するため、testEvent
というスケジュールを作成しました。absoluteBegin
が開始日、absoluteEnd
が終了日です。Unix時間のため分かりづらいですが2024年10月23日から10月25日を指定しています。
{
"version": "2019-03-31",
"events": [
{
"eventId": "grn:gs2:ap-northeast-1:XXX:schedule:game-0001:event:testEvent",
"name": "testEvent",
"scheduleType": "absolute",
"absoluteBegin": 1729609200000,
"absoluteEnd": 1729780200000,
"repeatSetting": {
"repeatType": "always",
"beginDayOfMonth": 1,
"endDayOfMonth": 1,
"beginHour": 0,
"endHour": 0,
"activeDays": 1,
"inactiveDays": 1
}
}
]
}
GS2-LoginRewardのマスタ設定
スケジュールモードの設定
testScheduleReward
という名前で、スケジュールモードのマスタを作成しています。
periodEventId
でGS2-ScheduleのtestEvent
と連携しています。これにより、testEvent
で設定した期間にこのマスタで設定した報酬を受け取れるようになります。
rewards
では、配列で各日の報酬(入手アクション)を定義しています。配列の1番目のアクションが、testEvent
の期間の初日報酬に該当し、以降2日目、3日目と続きます。
各日の報酬を設定するacquireActions
は配列なので、1日の中で複数の入手アクションを設定可能になっています。
今回は以下のような報酬設定になっています。
2024/10/24 | 2024/10/25 | 2024/10/26 |
---|---|---|
コイン x 1,000 | ジェム x 1,000 | チケットx 1,000 |
{
"bonusModelId": "grn:gs2:ap-northeast-1:XXX:loginReward:game-0001:bonusModel:testScheduleReward",
"name": "testScheduleReward",
"mode": "schedule",
"periodEventId": "grn:gs2:ap-northeast-1:XXX:schedule:game-0001:event:testEvent",
"resetHour": 0,
"rewards": [
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"coin\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 1000\n}"
}
]
},
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"gem\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 1000\n}"
}
]
},
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"ticket\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 1000\n}"
}
]
}
],
"missedReceiveRelief": "disabled",
"missedReceiveReliefVerifyActions": [],
"missedReceiveReliefConsumeActions": []
}
ストリーミングモードの設定
testStreamingReward
という名前で、ストリーミングモードのマスタを作成しています。スケジュールモードと異なりperiodEventId
の設定は不要です。
rewards
配列で設定した入手アクションがそのまま1日目、2日目、3日目の報酬となります。
今回は以下のような報酬設定になっています。
1日目 | 2日目 | 3日目 |
---|---|---|
コイン x 500 | ジェム x 500 | チケットx 500 |
{
"bonusModelId": "grn:gs2:ap-northeast-1:XXX:loginReward:game-0001:bonusModel:testStreamingReward",
"name": "testStreamingReward",
"mode": "streaming",
"resetHour": 0,
"repeat": "disabled",
"rewards": [
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"coin\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 500\n}"
}
]
},
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"gem\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 500\n}"
}
]
},
{
"acquireActions": [
{
"action": "Gs2Inventory:AcquireItemSetByUserId",
"request": "{\n \"namespaceName\": \"game-001\",\n \"inventoryName\": \"LoginRewardTest\",\n \"itemName\": \"ticket\",\n \"userId\": \"#{userId}\",\n \"acquireCount\": 500\n}"
}
]
}
],
"missedReceiveRelief": "disabled",
"missedReceiveReliefVerifyActions": [],
"missedReceiveReliefConsumeActions": []
}
Unityの実装
続いて、Unityで作成したゲームプロジェクトの内容をご紹介します。
ログイン後に報酬獲得APIを実行したり、UIコンポーネントを活用してそれぞれのログインボーナスの獲得状況を表示したりしています。
それぞれの機能別にどのようなコードで実装しているかを紹介していきます。
ログインボーナス取得処理
プレイヤーのログイン後に以下のGetLoginReward()
を実行しています。ReceiveAsync()により、ログイン中のユーザーとして指定した報酬モデルのボーナスを受け取っています。報酬モデル名はコンポーネント外から受け取る形にしており、今回のプロジェクト上ではtestScheduleReward
やtestStreamingReward
を指定しています。
また、APIを実行した結果何らかの理由で報酬が受け取れなかった場合は例外が投げられるようになっているため、catch()
によりエラー処理を行っています。
private async UniTask GetLoginReward()
{
try
{
var transaction = await gs2Data.Client
.LoginReward
.Namespace(nameSpace)
.Me(gs2Data.LoginUserGameSession)
.Bonus()
.ReceiveAsync(bonusModelName, config:null );
await transaction.WaitAsync(true);
}
catch (Exception e)
{
if (e.Message.Contains("outOfRange"))
{
Debug.Log(bonusModelName + ": 報酬提供範囲外です。");
}
else if (e.Message.Contains("notInSchedule"))
{
Debug.Log(bonusModelName + ": スケジュールの範囲外です。");
}
else if (e.Message.Contains("alreadyReceived"))
{
Debug.Log(bonusModelName + ": 既に報酬を取得済です。");
}
else
{
throw;
}
}
}
ログインボーナス取得状況リスト描画
プレイヤーがログインした後GetLoginReward()
を実行し、その後は以下のRefreshItemList()
を実行するようにしています。
このメソッドは現在ログインしているプレイヤーのログインボーナス獲得状況をUIに表示する処理を行っています。
GetRewardList()
のBonusModel().ModelAsync()
を通して取得したボーナスモデル情報内の報酬一覧情報と、GetReceiveStatuses()
のReceiveStatus().ModelAsync()
を通して取得したプレイヤーのログインボーナス獲得状況を組み合わせて、このユーザーがどの報酬を取得済なのかというリストを作成し、それをUIコンポーネントで表示しています。
報酬アイテム情報はAcquireActions
のJsonを展開して取得し、その後GS2-Inventoryの情報を格納している別コンポーネントと連携して日本語アイテム名情報も取得している形です。
public class RewardRequest
{
public string itemName;
public string acquireCount;
}
public class Gs2LoginReward : MonoBehaviour
{
// 一部省略
private async UniTask RefreshItemList()
{
var rewardList = await GetRewardList();
var receiveStatuses = await GetReceiveStatuses();
for (int i = 0; i < rewardList.Count; i++)
{
itemListValue.text = $"{i}:";
Instantiate(itemListValue, itemListContent.transform);
var status = "";
if (receiveStatuses.Count > i)
{
status = receiveStatuses[i] ? ":(済)" : "";
}
for (int j = 0; j < rewardList[i].AcquireActions.Count; j++)
{
var rewardRequest = JsonUtility.FromJson<RewardRequest>(rewardList[i].AcquireActions[j].Request);
var itemName = _gs2InventoryList.GetItemDisplayName(rewardRequest.itemName);
itemListValue.text = $" {itemName} x {rewardRequest.acquireCount}{status}";
Instantiate(itemListValue, itemListContent.transform);
}
}
}
// ログインボーナスモデル情報取得
private async UniTask<List<EzReward>> GetRewardList()
{
var model = await gs2Data.Client
.LoginReward
.Namespace(nameSpace)
.BonusModel(bonusModelName)
.ModelAsync();
return model.Rewards;
}
// ログインボーナス獲得状況取得
private async UniTask<List<bool>> GetReceiveStatuses()
{
var status = await gs2Data.Client
.LoginReward
.Namespace(nameSpace)
.Me(gs2Data.LoginUserGameSession)
.ReceiveStatus(bonusModelName)
.ModelAsync();
return status.ReceivedSteps;
}
}
実行結果
ゲームを起動すると自動的にユーザーがログインし、以下の画像のようにゲーム画面が表示されました。
起動したのは2024年10月24日で、スケジュールモードの報酬期間は2024年10月23日~25日で設定しているため、スケジュールモードのログインボーナスは2つ目のジェムx1000の報酬のみ(済)となっています。一方で、ストリーミングモードのログインボーナスについては、1つめのコインx500が(済)となっています。もし、2024年10月25日に再度起動した場合、各モードの次のアイテムが(済)となるでしょう。
現在の持ち物はログインユーザーのGS2-Inventoryのアイテム取得状況を描画しており、スケジュールモードの報酬であるジェム x 1000とストリーミングモードの報酬であるコインx500を獲得できていることが分かります。
まとめ
GS2-LoginRewardによってログインボーナス機能を実装する例をご紹介しました。
ログインボーナスはモバイルソーシャルゲームだけでなく一部のコンシューマゲームでも取り入れられています。
プレイヤーの継続プレイを促すのに有効な機能であり、GS2を使うことで簡単に利用することが可能となっています。